package io.kimmking.auth2.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.Objects;

/**
 * 配置授权服务
 */
@Configuration
@Slf4j
//注解方式启用授权服务
@EnableAuthorizationServer
public class Oauth2ServerConfig  extends AuthorizationServerConfigurerAdapter {

    private final static String prefix = "kmm";

    /**
     * token 对应权限信息的存储
     */
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    /**
     * 密码的校验方式
     */
    @Autowired
    private PasswordEncoder passwordEncoder;


    /**
     * token的存储方式
     * @return
     */
    @Bean(name = "tokenStore")
    public TokenStore tokenStore(RedisConnectionFactory connectionFactory) {
//        return new InMemoryTokenStore();
        RedisTokenStore tokenStore = new RedisTokenStore(Objects.requireNonNull(connectionFactory, "RedisConnectionFactory is null"));
        tokenStore.setPrefix(prefix);
        return tokenStore;
    }

    /**
     * 定义密码编码器的Bean
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(10);
    }

    @Bean(name = "authenticationProvider")
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return daoAuthenticationProvider;
    }

    /**
     * 授权服务配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                //自定义 身份验证管理器
                .authenticationManager(authentication -> {
                    log.info("authentication:{}", authentication);
                    return authenticationProvider().authenticate(authentication);
                })
                .exceptionTranslator(webResponseExceptionTranslator)
                //配置userDetailsService，此处在刷新token时需要用到
                .userDetailsService(userDetailsService)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    /**
     * 配置权限服务的安全配置
     * /oauth/check_token 接口调用时需要认证访问
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //容许check_token访问
        security.checkTokenAccess("permitAll()")
        //容许表单认证
        .allowFormAuthenticationForClients();
    }

    /**
     * 配置客户端信息,这里仅支持<密码模式>获取token
     * 当前端调用/oauth/token接口时，需要的Authorization信息，grant_type方式等
     * client_id 客户端ID
     * client_secret 客户端秘钥
     * 客户端信息一般是从数据库中获取，此处暂时在代码里固定
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("yy")
                .secret(passwordEncoder.encode("123456"))
                .scopes("ALL")
                .authorities("oauth2")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(3600);
    }
}
